/*
 * This program is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 * for more details.
 * 
 * You should have received a copy of the GNU General Public License along 
 * with this program; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <QStringList>
#include <QMap>
#include <QRegExp>
#include "wakabaparser.h"
#include "log.h"
#include "text.h"

namespace Wishmaster
{
	class WakabaParserImpl
	{
	public:
		WakabaParserImpl(const QString& boardname) : m_boardname(boardname) {}

		bool parseOpPost(PostInfo& info, const QByteArray& input);
		ThreadInfo parseThreadEntry(const QString& chanName, const QString& boardId, const QString& str, int number);

		QString m_boardname;
		TimestampParser m_timestampParser;
	};

	WakabaParser::WakabaParser(const QString& boardname) : BoardParser()
	{
		m_impl = new WakabaParserImpl(boardname);
	}

	WakabaParser::~WakabaParser()
	{
		delete m_impl;
	}

	ThreadInfo WakabaParserImpl::parseThreadEntry(const QString& chanName, const QString& boardId, const QString& str, int number)
	{
		ThreadInfo info;
		info.thread_id = ThreadId();
		QString thread_regexp = "\\[<a href=\"%BOARD%res/(\\d+)\\.html(?:#[^\"]*)?\">[^<]*</a>\\]\\s*<blockquote>(.*)</blockquote>";
		thread_regexp.replace(QString("%BOARD%"), m_boardname);
		QRegExp rx(thread_regexp);
		rx.setMinimal(true);
		int pos = rx.indexIn(str, 0);
		if(pos < 0)
			return info;

		info.thread_id = ThreadId(chanName, boardId, rx.cap(1).toULong());
		info.topic = htmlToPlainText(rx.cap(2)).trimmed();
		if(info.topic.isEmpty())
		{
			info.topic = QString("#") + info.thread_id.threadStringId();
		}

		QRegExp img("<img src=\"([^\"]*)\"[^>]*class=\"(?:thumb|img)\"[^>]*>");
		img.setMinimal(true);
		if(img.indexIn(str) > 0)
			info.leaderPicUrl.setPath(img.cap(1));

		QRegExp blockquote("<span class=\"(?:comment)?postername\">[^<]*</span>([^<]*)</label>.*<blockquote>");
		blockquote.setMinimal(true);
		int bqs = 0;
		pos = 0;
		int lastpos = pos;
		while((pos = blockquote.indexIn(str, pos)) >= 0)
		{
			bqs++;
			lastpos = pos;
			pos += blockquote.matchedLength();
			//log(llDebug, "%s/%d", qPrintable(blockquote.cap(1)), lastpos);
		}

		blockquote.indexIn(str, lastpos);
		//log(llDebug, "pos: %s/%s", qPrintable(blockquote.cap(1)), qPrintable(m_timestampParser.pattern()));
		QDateTime threadtime = m_timestampParser.parse(blockquote.cap(1));
		info.timestamp = threadtime.toTime_t();

		QRegExp omittedPosts("<span class=\"omittedposts\">\\D+(\\d+)\\D");
		omittedPosts.setMinimal(true);
		if(omittedPosts.indexIn(str) > 0)
			info.posts = bqs + omittedPosts.cap(1).toInt();
		else
			info.posts = bqs;

		//log(llDebug, "Thread: %d(%d) [%d/%s] leaderPic: %s", info.thread_id.threadNumericId(), info.posts, info.timestamp, qPrintable(threadtime.toString()), qPrintable(info.leaderPicUrl.toString()));
		return info;
	}

	QList<ThreadInfo> WakabaParser::getThreadList(const QString& chanName, const QString& boardId, const QByteArray& input)
	{
		QList<ThreadInfo> list;
		QString str = QString::fromUtf8(input);
		QRegExp hr("<\\s*hr[^>]*>");
		QStringList entries = str.split(hr);
		log(llDebug, "WakabaParser, got %d threads", entries.count());
		int i = 0;
		foreach(QString l, entries)
		{
			ThreadInfo info = m_impl->parseThreadEntry(chanName, boardId, l, i);
			if(info.thread_id.isValid())
				list.append(info);
			i++;
		}
		return list;
	}

	QList<PostInfo> WakabaParser::getPosts(const ThreadId& threadId, const QByteArray& input)
	{
		QList<PostInfo> list;
		QMap<PostId, int> postPositions;
		list.clear(); // just in case
		PostInfo oppost;
		if(!m_impl->parseOpPost(oppost, input))
		{
			log(llDebug, "Error, no OP post found.");
		}
		oppost.post_id = PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), threadId.threadNumericId());
		list.append(oppost);

		postPositions.insert(oppost.post_id, 0);

		QString thread = QString::fromUtf8(input);
		QRegExp thumbrx("<a target=\"_blank\" href=\"([^\"]*)\">.*<img src=\"([^\"]*)\"[^>]*class=\"(?:thumb|img)\" />");
		thumbrx.setMinimal(true);
		QRegExp rx("<input type=\"checkbox\" name=\"delete\" value=\"(\\d+)\" />\\s*<span class=\"replytitle\">([^<>]*)</span>[^<]*(?:<span class=\"(?:comment)?postername\">(.*)</span>)?(.*)</label>(.*)<blockquote>(.*)</blockquote> ?(?:</td>|<div)");
		rx.setMinimal(true);

		int pos = 0;
		int post_position = 1;
		while((pos = rx.indexIn(thread, pos)))
		{
			if(pos == -1)
				break;
			unsigned int postId = rx.cap(1).toULong();
			pos += rx.matchedLength();
			PostInfo post;
			post.topic = htmlToPlainText(rx.cap(2));
			post.text = rx.cap(6);
			post.name = htmlToPlainText(rx.cap(3));
			//log(llDebug, "%s", (rx.cap(4).toUtf8().toHex().data()));
			post.timestamp = m_impl->m_timestampParser.parse(rx.cap(4)).toTime_t();

			if((!rx.cap(5).isEmpty()) && (thumbrx.indexIn(rx.cap(5)) >= 0))
			{
				AttachmentInfo picture;
				picture.thumbUrl = QUrl(thumbrx.cap(2));
				picture.url = QUrl(thumbrx.cap(1));
				post.attachments.append(picture);
				log(llDebug, "Thumb: %s", qPrintable(picture.thumbUrl.toString()));
			}

			post.post_id = PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), postId);
			QList<PostId> links = findLinks(threadId, post.text);
			foreach(const PostId& link, links)
			{
				list[postPositions.value(link)].replies.insert(post.post_id);
			}

			list.append(post);
			postPositions.insert(post.post_id, post_position);
			post_position++;
		}
		return list;
	}

	QString WakabaParser::id()
	{
		return "wakaba";
	}

	bool WakabaParserImpl::parseOpPost(PostInfo& info, const QByteArray& input)
	{
		QString thread = QString::fromUtf8(input);
		QRegExp thumbrx("<a target=\"_blank\" href=\"([^\"]*)\">.*<img src=\"([^\"]*)\"[^>]*class=\"(?:thumb|img)\"[^>]*>");
		thumbrx.setMinimal(true);
		QRegExp rx("<span class=\"filetitle\">([^<>]*)</span>[^<]*(?:<span class=\"postername\">([^<>]*)</span>)?(.*)</label>.*<blockquote>(.*)</blockquote>");
		rx.setMinimal(true);

		int pos = 0;
		pos = rx.indexIn(thread, pos);
		if(pos == -1)
			return false;

		info.topic = htmlToPlainText(rx.cap(1));
		info.name = htmlToPlainText(rx.cap(2));
		info.text = rx.cap(4);
		info.timestamp = m_timestampParser.parse(rx.cap(3)).toTime_t();

		pos = thumbrx.indexIn(thread, 0);
		if(pos >= 0)
		{
			AttachmentInfo picture;
			picture.thumbUrl = QUrl(thumbrx.cap(2));
			picture.url = QUrl(thumbrx.cap(1));
			info.attachments.append(picture);
			log(llDebug, "opThumb: %s", qPrintable(picture.thumbUrl.toString()));
		}
		return true;
	}

	PostId WakabaParser::getPostID(const QString& link, const ThreadId& threadId)
	{
		if(link.trimmed().isEmpty())
			return PostId();
		QString regexp = QString("%1res/%2\\.html#(\\d+)").arg(threadId.boardId()).arg(threadId.threadNumericId());
		QRegExp rx(regexp);
		if(rx.indexIn(link) != -1)
		{
			return PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), rx.cap(1).toULong());
		}
		rx.setPattern(QString("%1res/%2\\.html").arg(threadId.boardId()).arg(threadId.threadNumericId()));
		if(rx.indexIn(link) != -1)
			return PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), threadId.threadNumericId());
		return PostId();
	}

	QList<PostId> WakabaParser::findLinks(const ThreadId& threadId, const QString& posttext)
	{
		QList<PostId> links;
		QRegExp rx("onclick=\"highlight\\((\\d+)\\)\"");
		int pos = 0;
		while((pos = rx.indexIn(posttext, pos)) != -1)
		{
			links.append(PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), rx.cap(1).toUInt()));
			pos += rx.matchedLength();
		}
		return links;
	}

	BoardParser* WakabaParser::clone(const QString& boardname)
	{
		return new WakabaParser(boardname);
	}

	void WakabaParser::setTimestampFormat(const QString& format)
	{
		m_impl->m_timestampParser.setPattern(format);
	}
}


Q_EXPORT_PLUGIN2(wakabaparser, Wishmaster::WakabaParser)


